﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

using Noris.Tools.TraceVisualiser.Interface;
using Noris.Tools.TraceVisualiser.TraceData;
using Noris.Tools.TraceVisualiser.Support;

namespace Noris.Tools.TraceVisualiser.DemoReader
{
	public class DemoReaderSimple : ITraceReader
	{
		#region IPlugin Members
		/// <summary>
		/// Příznak, že Plugin je aktivní ve standardním běhu (tj. při běžném užiavtelském spuštění).
		/// Plně funkční pluginy tedy vrací true.
		/// Pluginy, které jsou ve vývoji, vrací false, takové jsou zobrazeny / spouštěny pouze v aplikaci, která běží v režimu Debug.
		/// </summary>
		bool IPlugin.Active { get { return false; } }
		#endregion
		#region ITraceReader Members
        /// <summary>
        /// Zde reader vrací výraz, který se použije v dialogu při výběru vstupních trace soubor.
        /// Různé readery mají různé vstupní soubory s různou příponou, a my nechceme žádný reader opomenout.
        /// Reader může umět zpracovávat více různých koncovek, proto se používá string[], každá položka obsahuje jeden filtr.
        /// Každý jednotlivý filtr je ve tvaru: "Textový popisek|filtr", například: "Aplikační trace Green (*.csv)|*csv".
        /// </summary>
        public string[] FileOpenFilters
        {
            get
            {
                return new string[]
                {
                    "Simple trace file (*.xml)|*.xml"
                };
            }
        }
        /// <summary>
		/// <para>
		/// Metoda má za úkol podívat se na název souboru,
		/// a vyjádřit se k tomu, zda se bude chtít podívat i dovnitř souboru (tím, že nastaví args.CheckContent = true).
		/// Metoda by měla (pokud se tento TraceReader dokáže s názvem souboru vypořádat) rozebrat název souboru na jméno serveru, a oddělit i pořadové číslo z názvu.
		/// Metoda absolutně nemá za úkol otevírat soubor, pracuje jen s názvem.
		/// Pokud se metoda bude chtít podívat do souboru, nastaví args.CheckContent = true, a za chvíli se vyvolá další metoda: this.CheckContent().
		/// </para>
		/// <para>
		/// V této metodě by bylo možno deklarovat vlastnosti serveru a režimy zobrazování, ukládat je do args (a tamodtud pak do serveru při jeho prvním zakládání)
		/// </para>
		/// </summary>
		/// <param name="args">Data o souboru (název a výsledky této metody)</param>
		/// <returns></returns>
		public void CheckFileName(TraceReaderCheckNameArgs args)
		{
			// Nechci se dívat dovnitř (možná časem změním názor):
			args.CheckContent = false;

			// Přípona má být ".text"
			System.IO.FileInfo fileInfo = args.FileInfo;
			if (!String.Equals(fileInfo.Extension, ".xml", StringComparison.OrdinalIgnoreCase))
				return;

			// Vypadá to nadějně, určíme název serveru (adresář, základ jména):
			// Název serveru si můžeme klidně vymyslet, slouží jako textový klíč.
			// V rámci serveru se ukládají jednotlivé procesy, a v procesech jednotlivé události (viz později).
			string fileName = System.IO.Path.GetFileNameWithoutExtension(fileInfo.Name);
			args.ServerName = System.IO.Path.Combine(fileInfo.Directory.Name, fileName) + "_Simple_" + fileInfo.LastWriteTime.Date.ToString("yyyyMMdd");

			// Šlo by to, podíváme se dovnitř:
			args.CheckContent = true;
		}
		/// <summary>
		/// Metoda je volaná tehdy, když tento reader se přihlásil ke čtení záhlaví souboru (v metodě CheckFileName, nastavil args.CheckContent = true).
		/// Tato metoda nemá za úkol číst data, ale podívat se dovnitř, a usoudit, zda my chceme tento soubor zpracovávat.
		/// Soubor je otevře, a je k němu přístup přes argument
		/// </summary>
		/// <param name="args"></param>
		public void CheckContent(TraceReaderCheckContentArgs args)
		{
			// Nechci soubor zpracovávat (možná časem změním názor):
			args.Priority = 0M;

			// Soubor by měl začínat takhle:
			string el1 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
			string el2 = "<tracedata version=\"1.0\" source=\"hand-made\">";
			// Ověříme to:
			string rl1 = args.FileStreamReader.ReadLine();
			if (rl1 != el1)
				return;
			string rl2 = args.FileStreamReader.ReadLine();
			if (rl2 != el2)
				return;

			// Najdu začátek a konec dat v souboru, ale neměl bych číst celý soubor (může mít desítky Megabyte):
			// Pokud ale nemohu jinak, tak čtu vše...
			string first = null;
			string last = null;
			string start = "<event Time=\"";
			// Řádek má být: <event Time="2011-09-08;12:45:00.133">  (index počátku data = 13, délka nejméně 37)
			//               01234567890123456789012345678901234567
			while (!args.FileStreamReader.EndOfStream)
			{
				string line = args.FileStreamReader.ReadLine().Trim();
				if (line.StartsWith(start) && line.Length >= 37)
				{
					if (first == null)
						first = line;
					last = line;
				}
			}

			// Pokud máme něco nalezeno, vytvořím z toho datumy a časový interval:
			TimeRange contentTime = TimeRange.Empty;
			if (last != null)
			{
				System.Globalization.DateTimeFormatInfo dtfi = new System.Globalization.DateTimeFormatInfo();
				dtfi.FullDateTimePattern = "yyyy-MM-dd;HH:mm:ss.fff";
				DateTime begin = ConvertToDateTime(first.Substring(13, 23));
				DateTime end = ConvertToDateTime(last.Substring(13, 23));
				if (begin > DateTime.MinValue && end > DateTime.MinValue)
					contentTime = new TimeRange(begin, end);
			}
			if (contentTime.IsEmpty)
				return;

			// Tento soubor bych mohl zpracovat:
			args.Priority = 2M;
			args.ContentTime = contentTime;

			// Připravím texty:
			args.ElementTexts = new string[] { args.FileInfo.Name, args.FileInfo.Directory.Name + "\\" + args.FileInfo.Name };
			args.ElementTooltip = "Simple text: " + args.FileInfo.FullName + Environment.NewLine + "Time: " + args.ContentTime.StringFullDetail;

			// Připravím základní řádek grafu (root):
			TreePath path = CreateRootPath();
			args.TreePaths = new TreePath[] { path };                 // args.TreePaths může pojmout více root položek (pokud jeden trace reader zakládá více řádků v root úrovni)
		}
		/// <summary>
		/// Vytvoří a vrátí plně definovanou cestu k root řádku tohoto grafu
		/// </summary>
		/// <returns></returns>
		internal static TreePath CreateRootPath()
		{
			TreePath path = new TreePath(true);                               // toto je objekt, který registruje celou cestu (více úrovní)
			path.AddItem(CreateTreePathItem("SimpleRoot", "Simple trace", true));   // toto je objekt, který popisuje jednu úroveň tree
			return path;
		}
		/// <summary>
		/// Vytvoří a vrátí prvek cesty k nodu, standardní graf.
		/// </summary>
		/// <param name="key">Klíč nodu</param>
		/// <param name="text">Text v nodu</param>
        /// <param name="expectChilds">Očekáváme, že tento node bude mít Childs?</param>
		/// <returns></returns>
		internal static TreePathItem CreateTreePathItem(string key, string text, bool expectChilds)
		{
			//  - deklarace grafu:
			Components.GraphRowDeclarationCls gd = new Components.GraphRowDeclarationCls();
			gd.ActionOnElementDoubleClick = Components.RowActionType.ZoomTimeToCurrentElements;
			gd.ActionOnRowDoubleClick = Components.RowActionType.ZoomTimeToAllElementsOnRow;
			// Základní vlastnosti (elementy načítá v případě potřeby, subnody třídí dle klíče Child, prvotní elementy načítá rovnou při načtení řádku (omezí tak další request na čtení elementů):
            gd.Properties = Components.RowProperties.DynamicElementsLoading | Components.RowProperties.ChildSortedKey | Components.RowProperties.PreLoadingElements;
            // Pokud očekáváme, že tento node bude mít subChilds, pak zajistíme zobrazení [+], a childs se načtou po rozbalení tohoto nodu:
            if (expectChilds)
                gd.Properties |= Components.RowProperties.DynamicSubNodeLoading;

			gd.RowGraph = Components.RowGraphMode.MultiTask;
			gd.RowHeightLineSolidPx = 12;
			gd.RowHeightTotalMaxPx = 180;
			gd.ShowPrevNextIcon = true;
			
			return new TreePathItem(key, text, gd);   // toto je objekt, který popisuje jednu úroveň tree
		}
		/// <summary>
		/// Metoda je volaná pro zpracování celého obsahu dat.
		/// Veškerou podporu má v argumentu TraceReaderReadItemArgs args.
		/// Metoda si může libovolně zpracovat vstupující soubor 
		/// a vytvářet z něj data ve formě jednotlivých záznamů typu ITraceItem.
		/// Tyto záznamy předává ke zpracování do datového systému pomocí volání metody argumentu ProcessItem(ITraceItem item).
		/// Metoda takto může vynechat některé věty (nedávat je ke zpracování), anebo některé věty zpracovat více způsoby
		/// (například z jedné věty trace vytvoří dvě položky: jednu ktertá představuje aktivitu uživatele, a druhou která popisuje stav paměti).
		/// Tím, že zpracování probíhá v jedné metodě, si reader může udržovat kontinuální povědomí o načítaných datech, 
		/// a například o stavu paměti posílat položky při změně hodnoty (není třeba posílat 1000 x jednu hodnotu stavu paměti).
		/// </summary>
		/// <param name="args"></param>
		public void ProcessContent(TraceReaderProcessContentArgs args)
		{
			// Filestream GoBegin:
			args.FileStreamReader.BaseStream.Position = 0;
			args.FileStreamReader.DiscardBufferedData();

			// Read via XML:
			using (System.Xml.XmlReader xmlReader = System.Xml.XmlReader.Create(args.FileStreamReader))
			{
				while (!xmlReader.EOF)
				{
					if (xmlReader.NodeType == System.Xml.XmlNodeType.Element && xmlReader.LocalName == "event")
					{
						DemoEventSimple demo = DemoEventSimple.CreateFromXml(xmlReader);
						if (demo != null)
							args.ProcessEvent(demo);
					}
					xmlReader.Read();
				}
			}
		}
		/// <summary>
		/// Tato metoda má za úkol vytvořit a vrátit logickou událost na základě fyzických dat načtených z trace
		/// (před malinkou chvilkou je objekt osobně načetl a vrátil v metodě ReadItem()).
		/// Pokud se vrátí null, pak tato položka trace nemá být zakládána do evidence.
		/// </summary>
		public IEventItem CreateIEventItem(IEventEdge data)
		{
			// Tuto metodu nebudeme využívat, protože při načítání trace budeme vytvářet přímo objekty IEventItem, nikoli IEventEdge.
			return null;
		}
		/// <summary>
		/// Metoda, která má za úkol vytvořit celou plně definovanou cestu k nodu.
		/// Tato metoda se volá zcela výjimečně - tehdy, když tento objekt předepisuje nějakou TargetPath, která dosud neexistuje.
		/// Pak si jádro vyžádá plnou cestu k nodu, včetně textů a definice všech nodů.
		/// </summary>
		/// <param name="args">Data pro vytvoření cesty</param>
		/// <returns></returns>
		public void CreateFullTreePath(TraceReaderCreatePathArgs args)
		{
			// Tuto metodu nebudeme využívat, protože do každého vytvořeného objektu IEventItem vložíme plnou cestu včetně deklarace řádku a grafu.
		}
		#endregion
        #region Konverze typů
		internal static DateTime ConvertToDateTime(string time)
		{
			_PrepareConvertors();

			DateTime result;
			if (!String.IsNullOrEmpty(time) && time.Length >= 10 && DateTime.TryParseExact(time, "F", _DateTimeFmt, _DateTimeStl, out result))
				return result;
			return DateTime.MinValue;
		}
		internal static double ConvertToDouble(string value)
		{
			_PrepareConvertors();
			double result;
			if (!String.IsNullOrEmpty(value) && Double.TryParse(value, System.Globalization.NumberStyles.Any, _NumberFmt, out result))
				return result;
			return 0D;
		}
		private static void _PrepareConvertors()
		{
			if (_DateTimeFmt == null)
			{
				_DateTimeFmt = new System.Globalization.DateTimeFormatInfo();
				_DateTimeFmt.FullDateTimePattern = "yyyy-MM-dd;HH:mm:ss.fff";
				_DateTimeStl = (System.Globalization.DateTimeStyles)(System.Globalization.DateTimeStyles.AllowWhiteSpaces | System.Globalization.DateTimeStyles.NoCurrentDateDefault);
			}
			if (_NumberFmt == null)
			{
				_NumberFmt = new System.Globalization.NumberFormatInfo();
				_NumberFmt.NumberDecimalDigits = 3;
				_NumberFmt.NumberDecimalSeparator = ".";
				_NumberFmt.NumberGroupSeparator = " ";
				_NumberFmt.NumberGroupSizes = new int[] { 3, 3, 3, 3, 3, };
			}
		}
		private static System.Globalization.DateTimeFormatInfo _DateTimeFmt;
		private static System.Globalization.DateTimeStyles _DateTimeStl;
		private static System.Globalization.NumberFormatInfo _NumberFmt;
		#endregion
		#region Vzorek dat
		/*   Vzorek vstupního souboru :
<?xml version="1.0" encoding="utf-8"?>
<tracedata version="1.0" source="hand-made">
   <event Time="2011-09-08;12:45:00.133" Duration="5.740">
      <data Login="kaj" Action="function" Method="run" Function="1650" Folder="122" Records="7450,6451,5452" />
   </event>
   <event Time="2011-09-08;12:45:00.145" Duration="10.260">
      <data Login="kaj" Action="sql" Command="SELECT TOP 1 poradac FROM lcs.poradace WHERE cislo_tridy=1188" />
   </event>
   <event Time="2011-09-08;12:46:04.261" Duration="32.316">
      <data Login="kaj" Action="record" Method="load" Class="201" Record="65512" />
   </event>
   <event Time="2011-09-08;12:47:14.840" Duration="0.183">
      <data Login="kaj" Action="record" Method="save" Class="201" Record="65512" />
   </event>
</tracedata>
		*/
		#endregion
        #region IDisposable Members
        public void Dispose()
		{
		}
		#endregion
	}
	/// <summary>
	/// Třída, která představuje datovou a vizuální událost
	/// </summary>
    public class DemoEventSimple : IEventItem
	{
		#region Naše vlastní data. Načítání z XML. Sestavení hodnot z XML dat do jednotlivých vizuálních property.
		public DemoEventSimple()
		{
			this.Values = new Dictionary<string, string>();
		}
		public Dictionary<string, string> Values { get; set; }
		public static DemoEventSimple CreateFromXml(System.Xml.XmlReader xmlReader)
		{
			DemoEventSimple demo = new DemoEventSimple();

			demo.FillFromXml(xmlReader);
			demo.PrepareData();

			return demo;
		}
		/// <summary>
		/// Načte do sebe hodnoty z dodaného elementu XML readeru. Hodnoty jsou načítány jako String.
		/// </summary>
		/// <param name="xmlReader"></param>
		protected void FillFromXml(System.Xml.XmlReader xmlReader)
		{
			string name = xmlReader.LocalName;
			int depth = xmlReader.Depth;
			Stack<string> nameStack = new Stack<string>();
			string prefix = "";
			while (!xmlReader.EOF)
			{
				// Pokud jsme našli konec výchozího elementu, skončíme:
				if (xmlReader.NodeType == System.Xml.XmlNodeType.EndElement && xmlReader.LocalName == name && xmlReader.Depth == depth)
					break;

				// Zpracujeme atributy:
				if (xmlReader.HasAttributes)
				{
					bool hasAtt = xmlReader.MoveToFirstAttribute();
					while (hasAtt)
					{
						string attName = xmlReader.LocalName;
						string attValue = xmlReader.Value;

						string key = prefix + attName;
						if (key != null)
							this.ValueAdd(key, attValue);

						hasAtt = xmlReader.MoveToNextAttribute();
					}
				}

				// Pokud najdeme nějaký vnitřní element, zpracujeme jej jako podřízený prvek (do klíče se jménem s prefixem dle jména elementu):
				while (!xmlReader.EOF)
				{
					xmlReader.Read();
					if (xmlReader.NodeType == System.Xml.XmlNodeType.Whitespace)
						continue;
					if (xmlReader.NodeType == System.Xml.XmlNodeType.Element)
					{
						nameStack.Push(prefix);
						prefix += xmlReader.Name + ".";
						break;
					}
					if (xmlReader.NodeType == System.Xml.XmlNodeType.EndElement)
					{
						prefix = nameStack.Pop();
						break;
					}
				}
			}

		}
		/// <summary>
		/// Přidá do sebe další hodnotu Value
		/// </summary>
		/// <param name="key"></param>
		/// <param name="value"></param>
		protected void ValueAdd(string key, string value)
		{
			if (key == null) return;
			if (this.Values.ContainsKey(key))
				this.Values[key] = value;
			else
				this.Values.Add(key, value);
		}
		/// <summary>
		/// Z dat načtených z XML naplní vizuální property.
		/// </summary>
		protected void PrepareData()
		{
			// Time:
			string time, duration;
			if (this.Values.TryGetValue("Time", out time))
			{
				if (this.Values.TryGetValue("Duration", out duration))
					this.PrepareTime(time, duration);
				else
					this.PrepareTime(time, "0");
			}

			// Process:
			string login;
			this.ProcessKey = "Simple";
			if (this.Values.TryGetValue("data.Login", out login))
				this.ProcessKey += "." + login;

			// Barva:
			this.BackColor = this.GetBackColor();

			this.Captions = this.CreateCaptions();
			this.ToolTipInfo = this.CreateToolTip();
			this.CaptionShowAllways = true;
			this.ElementLayer = Components.GraphLayerType.ElementItemLayer;
			this.ElementShapeType = Components.GraphElementShapeType.Hexagon;
			this.ElementVisible = true;
			this.Properties = Components.ElementProperties.Selectable;
			this.TreePaths = this.CreateTreePaths();
			this.Visible = true;
		}
		protected void PrepareTime(string time, string duration)
		{
			DateTime begin = DemoReaderSimple.ConvertToDateTime(time);
			double seconds = DemoReaderSimple.ConvertToDouble(duration);
			this.Time = new TimeRange(begin, seconds);
		}
		protected string[] CreateCaptions()
		{
			List<string> captions = new List<string>();
			
			string action;
			if (this.Values.TryGetValue("data.Action", out action))
			{
				captions.Add(action);
				action += ".";
			}
			else
				action = "";

			string method;
			if (this.Values.TryGetValue("data.Method", out method))
				captions.Add(action + method);

			return captions.ToArray();
		}
		protected string CreateToolTip()
		{
			string tool = "Událost v trace:" + Environment.NewLine +
				"Čas: " + this.Time.StringFullDetail + Environment.NewLine;
			foreach (KeyValuePair<string, string> value in this.Values)
				tool += value.Key + ": " + value.Value + Environment.NewLine;
			return tool;
		}
		protected Color GetBackColor()
		{
			string action;
			if (this.Values.TryGetValue("data.Action", out action))
			{
				switch (action)
				{
					case "function":
						return Color.Lime;
					case "sql":
						return Color.LightBlue;
					case "record":
						return Color.Tomato;
				}
			}
			return Color.Azure;
		}
		protected TreePath[] CreateTreePaths()
		{
			TreePath path = DemoReaderSimple.CreateRootPath();

			string login;
			if (this.Values.TryGetValue("data.Login", out login))
				path.AddItem(DemoReaderSimple.CreateTreePathItem(login, login, false));

			return new TreePath[] { path };
		}
		#endregion
		#region IEventPointer Members
		public string ProcessKey { get; set; }
		public string EventKey { get; set; }
		public int LineNumber { get; set; }
		#endregion
		#region IEventItem Members
		public string MergeKey { get; set; }
		public string TargetHomeNode { get; set; }
		public string[] TargetPaths { get; set; }
		public TreePath[] TreePaths { get; set; }

		public TimeRange Time { get; set; }

		public void AddData(IEventEdge eventEdge, TraceInfo traceInfo) { /* nepotřebujeme */ }
		public bool MergeWith(IEventItem nextEventItem) { return false;  /* nepotřebujeme */ }

		public string ColorGroup { get; set; }
		public int ColorIndex { get; set; }
		public bool Visible { get; set; }
		#endregion
		#region IDataElement Members
		public bool ElementVisible { get; set; }
		public GID GId { get; set; }
		public TimeRange TimeRange { get { return this.Time; } set { this.Time = value; } }
		public float ElementHeightRatio { get; set; }
		public bool BeginIsUncertain { get; set; }
		public bool EndIsUncertain { get; set; }
		public int SubTimeSort { get; set; }
		public bool FullRow { get; set; }
		public bool HideZeroWidthElementRectangle { get; set; }
		public float[] Fragments { get; set; }
		public Components.GraphLayerType ElementLayer { get; set; }
		public int ElementGroup { get; set; }
		public Components.GraphElementShapeType ElementShapeType { get; set; }
		public Components.ElementProperties Properties { get; set; }
        public Components.ElementActionType ActionOnDoubleClick { get { return Components.ElementActionType.DefaultByRow; } }
        public Components.GraphElementHandler DoubleClick { get { return null; } }
        public System.Drawing.Color BackColor { get; set; }
		public System.Drawing.Color FontColor { get; set; }
		public bool CaptionShowAllways { get; set; }
		public System.Drawing.Image ElementImage { get; set; }
		public System.Drawing.ContentAlignment ImagePivotElementPoint { get; set; }
		public System.Drawing.ContentAlignment ImagePivotImagePoint { get; set; }
		public string ToolTipInfo { get; set; }
		public string[] Captions { get; set; }
		
		public string GetToolTipInfo(Components.IDataRow rowData) { return null; }
		public string GetCaption(Components.IDataRow rowData, float charCount) { return null; }
		public void DrawElement(Components.GraphElementPainterArgs args) { }

		#endregion
		#region IDisposable Members

		public void Dispose()
		{
			
		}

		#endregion
	}
}
